#define _CRT_SECURE_NO_DEPRECATE
#include <stdio.h>
#include <windows.h>
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

struct B3DHeader
{
	std::string hText;
	int         cLen;
};

struct B3DTexture
{
	std::string file;
	int         flags;
	int         blend;
	float       x_pos;
	float       y_pos;
	float       x_scale;
	float       y_scale;
	float       rotation;
};

struct B3DMaterial
{
	std::string   name;
	float         r, g, b, a;
	float         shininess;
	int           blend;
	int           fx;
	int         * texture_id;
};

struct B3DVertex
{
	int                              flags;
	float                            x, y, z;
	float                            nx, ny, nz;
	float                            red, green, blue, alpha;
	std::vector<std::vector<float> > tex_coords;
};

struct B3DTriangle
{
	int brush_id;
	int vertex_id[3];
};

struct B3DBone
{
	int   vertex_id;
	float weight;
};

struct B3DKey
{
	int   flags;
	int   frame;
	float position[3];
	float scale[3];
	float rotation[4];
};

struct B3DAnimation
{
	int   flags;
	int   frames;
	float fps;
};

struct B3DMesh
{
	int                      brush_id;
	std::vector<B3DVertex>   vertexs;
	std::vector<B3DTriangle> tris;
};

struct B3DNode
{
	std::string               name;
	float                     position[3];
	float                     scale[3];
	float                     rotation[4];
	std::vector<B3DMesh>      meshes;
	std::vector<B3DBone>      bones;
	std::vector<B3DKey>       keys;
	std::vector<B3DNode>      nodes;
	std::vector<B3DAnimation> animations;
};

struct B3DFile
{
	int                       version;
	std::string               filename;
	std::vector<B3DTexture>   textures;
	std::vector<B3DMaterial>  materials;
	std::vector<B3DVertex>    vertexs;
	std::vector<B3DTriangle>  triangles;
	std::vector<B3DBone>      bones;
	std::vector<B3DKey>       keys;
	std::vector<B3DAnimation> animations;
	std::vector<B3DMesh>      meshes;
	std::vector<B3DNode>      nodes;
};

B3DHeader ReadHeader(FILE * f)
{
	char text[4];
	fread(text, 1, 4, f);
	text[4] = '\0';
	int len[1];
	fread((void *)len, 4, 1, f);
	B3DHeader temp;
	temp.cLen = len[0];
	temp.hText = text;
	return temp;
}

int ReadInt(FILE * f)
{
	int len[1];
	fread((void *)len, 4, 1, f);
	return len[0];
}

float ReadFloat(FILE * f)
{
	float len[1];
	fread((void *)len, 4, 1, f);
	return len[0];
}

B3DTexture ReadTexture(FILE * f)
{
	B3DTexture tex;
	std::string file;
	char buff[1];
	fread(buff, 1, 1, f);
	while(buff[0] != '\0')
	{
		file += buff[0];
		fread(buff, 1, 1, f);
	}
	int pos = file.find_last_of("/");
	if(pos == file.npos){pos = file.find_last_of("\\");}
	if(pos == file.npos)
	{tex.file = file;}
	else
	{tex.file = file.substr(pos + 1);}
	tex.flags = ReadInt(f);
	tex.blend = ReadInt(f);
	tex.x_pos = ReadFloat(f);
	tex.y_pos = ReadFloat(f);
	tex.x_scale = ReadFloat(f);
	tex.y_scale = ReadFloat(f);
	tex.rotation = ReadFloat(f);
	return tex;
}

B3DMaterial ReadMaterial(FILE * f, int n_texs)
{
	B3DMaterial mat;
	char buff[1];
	fread(buff, 1, 1, f);
	while(buff[0] != '\0')
	{
		mat.name += buff[0];
		fread(buff, 1, 1, f);
	}
	mat.r = ReadFloat(f);
	mat.g = ReadFloat(f);
	mat.b = ReadFloat(f);
	mat.a = ReadFloat(f);
	mat.shininess = ReadFloat(f);
	mat.blend = ReadInt(f);
	mat.fx = ReadInt(f);
	mat.texture_id = new int[n_texs];
	for(int i = 0; i < n_texs; i++)
	{
		mat.texture_id[i] = ReadInt(f);
	}
	return mat;
}

B3DVertex ReadVertex(FILE * f, int flags, int tex_coord_sets, int tex_coord_set_size)
{
	B3DVertex ver;
	ver.flags = flags;
	ver.x = ReadFloat(f);
	ver.y = ReadFloat(f);
	ver.z = ReadFloat(f);
	if(flags == 1)
	{
		ver.nx = ReadFloat(f);
		ver.ny = ReadFloat(f);
		ver.nz = ReadFloat(f);
	}
	else if(flags == 2)
	{
		ver.red = ReadFloat(f);
		ver.green = ReadFloat(f);
		ver.blue = ReadFloat(f);
		ver.alpha = ReadFloat(f);
	}
	for(int i = 0; i < tex_coord_sets; i++)
	{
		std::vector<float> a2;
		for(int j = 0; j < tex_coord_set_size; j++)
		{
			a2.push_back(ReadFloat(f));
		}
		ver.tex_coords.push_back(a2);
	}
	return ver;
}

B3DTriangle ReadTriangle(FILE * f, int brush_id)
{
	B3DTriangle tri;
	tri.brush_id = brush_id;
	tri.vertex_id[0] = ReadInt(f);
	tri.vertex_id[1] = ReadInt(f);
	tri.vertex_id[2] = ReadInt(f);
	return tri;
}

B3DKey ReadKey(FILE * f, int flags)
{
	B3DKey key;
	key.flags = flags;
	key.frame = ReadInt(f);
	switch(flags)
	{
		case 1:
			key.position[0] = ReadFloat(f);
			key.position[1] = ReadFloat(f);
			key.position[2] = ReadFloat(f);
		break;
		case 2:
			key.scale[0] = ReadFloat(f);
			key.scale[1] = ReadFloat(f);
			key.scale[2] = ReadFloat(f);
		break;
		case 4:
			key.rotation[0] = ReadFloat(f);
			key.rotation[1] = ReadFloat(f);
			key.rotation[2] = ReadFloat(f);
			key.rotation[3] = ReadFloat(f);
		break;
	}
	return key;
}

B3DBone ReadBone(FILE * f)
{
	B3DBone bone;
	bone.vertex_id = ReadInt(f);
	bone.weight = ReadFloat(f);
	return bone;
}

void ParseB3DFile(FILE * input, long len, B3DFile * b3dfile, B3DMesh * nmesh = NULL, B3DNode * nnode = NULL)
{
	B3DHeader header;
	while(len != ftell(input))
	{
		header = ReadHeader(input);
		if(header.hText == "TEXS")
		{
			long dest_len = ftell(input) + header.cLen;
			while(dest_len != ftell(input))
			{
				B3DTexture tex = ReadTexture(input);
				b3dfile->textures.push_back(tex);
			}
		}
		else if(header.hText == "BRUS")
		{
			long dest_len = ftell(input) + header.cLen;
			int n_texs = ReadInt(input);
			while(dest_len != ftell(input))
			{
				B3DMaterial mat = ReadMaterial(input, n_texs);
				b3dfile->materials.push_back(mat);
			}
		}
		else if(header.hText == "VRTS")
		{
			long dest_len = ftell(input) + header.cLen;
			int flags = ReadInt(input);
			int tex_coord_sets = ReadInt(input);
			int tex_coord_set_size = ReadInt(input);
			while(dest_len != ftell(input))
			{
				if(nmesh != NULL)
				{
					B3DVertex ver = ReadVertex(input, flags, tex_coord_sets, tex_coord_set_size);
					nmesh->vertexs.push_back(ver);
				}
				else
				{
					B3DVertex ver = ReadVertex(input, flags, tex_coord_sets, tex_coord_set_size);
					b3dfile->vertexs.push_back(ver);
				}
			}
		}
		else if(header.hText == "TRIS")
		{
			long dest_len = ftell(input) + header.cLen;
			int brush_id = ReadInt(input);
			while(dest_len != ftell(input))
			{
				if(nmesh != NULL)
				{
					B3DTriangle tri = ReadTriangle(input, brush_id);
					nmesh->tris.push_back(tri);
				}
				else
				{
					B3DTriangle tri = ReadTriangle(input, brush_id);
					b3dfile->triangles.push_back(tri);
				}
			}
		}
		else if(header.hText == "KEYS")
		{
			long dest_len = ftell(input) + header.cLen;
			int flags = ReadInt(input);
			while(dest_len != ftell(input))
			{
				B3DKey key = ReadKey(input, flags);
				if(nnode != NULL)
				{
					nnode->keys.push_back(key);
				}
				else
				{
					b3dfile->keys.push_back(key);
				}
			}
		}
		else if(header.hText == "BONE")
		{
			long dest_len = ftell(input) + header.cLen;
			while(dest_len != ftell(input))
			{
				B3DBone bon = ReadBone(input);
				if(nnode != NULL)
				{
					nnode->bones.push_back(bon);
				}
				else
				{
					b3dfile->bones.push_back(bon);
				}
			}
		}
		else if(header.hText == "ANIM")
		{
			B3DAnimation anim;
			anim.flags = ReadInt(input);
			anim.frames = ReadInt(input);
			anim.fps = ReadFloat(input);
			if(nnode != NULL)
			{
				nnode->animations.push_back(anim);
			}
			else
			{
				b3dfile->animations.push_back(anim);
			}
		}
		else if(header.hText == "MESH")
		{
			long dest_len = ftell(input) + header.cLen;
			B3DMesh mesh;
			mesh.brush_id = ReadInt(input);
			ParseB3DFile(input, dest_len, b3dfile, &mesh);
			if(nnode != NULL)
			{
				nnode->meshes.push_back(mesh);
			}
			else
			{
				b3dfile->meshes.push_back(mesh);
			}
		}
		else if(header.hText == "NODE")
		{
			long dest_len = ftell(input) + header.cLen;
			B3DNode node;
			char buff[1];
			fread(buff, 1, 1, input);
			while(buff[0] != '\0')
			{
				node.name += buff[0];
				fread(buff, 1, 1, input);
			}
			node.position[0] = ReadFloat(input);
			node.position[1] = ReadFloat(input);
			node.position[2] = ReadFloat(input);
			node.scale[0] = ReadFloat(input);
			node.scale[1] = ReadFloat(input);
			node.scale[2] = ReadFloat(input);
			node.rotation[0] = ReadFloat(input);
			node.rotation[1] = ReadFloat(input);
			node.rotation[2] = ReadFloat(input);
			node.rotation[3] = ReadFloat(input);
			ParseB3DFile(input, len, b3dfile, NULL, &node);
			if(nnode != NULL)
			{
				nnode->nodes.push_back(node);
			}
			else
			{
				b3dfile->nodes.push_back(node);
			}
		}
		else
		{
			fseek(input, header.cLen, SEEK_CUR);
		}
	}
}

bool ReadB3DFile(const char * filename, B3DFile * b3dfile)
{
	FILE * input = fopen(filename, "r+b");
	if(input == NULL)
	{
		std::cout << "File not found!" << std::endl;
		return false;
	}
	fseek(input, 0, SEEK_END);
	long len = ftell(input);
	fseek(input, 0, SEEK_SET);
	B3DHeader header = ReadHeader(input);
	if(header.hText != "BB3D")
	{
		std::cout << "It is not .B3D file!" << std::endl;
		return false;
	}
	int ver = ReadInt(input);
	b3dfile->filename = filename;
	b3dfile->version = ver;
	ParseB3DFile(input, len, b3dfile);
	return true;
}

void PrintSubnodes(B3DNode * node, int level)
{
	for(int i = 0; i < node->nodes.size(); i++)
	{
		for(int j = 0; j < level; j++)
		{
			std::cout << " ";
		}
		std::cout << "Node: " << node->nodes[i].name << " Meshes - " << node->nodes[i].meshes.size() << " Bones - " << node->nodes[i].bones.size() << " Nodes - " << node->nodes[i].nodes.size() << std::endl;
		for(j = 0; j < node->nodes[i].meshes.size(); j++)
		{
			for(int k = 0; k < level; k++)
			{
				std::cout << " ";
			}
			std::cout << " Mesh vertexs: " << node->nodes[i].meshes[j].vertexs.size() << " Triangles: " << node->nodes[i].meshes[j].tris.size() << std::endl;
		}
		PrintSubnodes(&node->nodes[i], level + 1);
	}
}

int main()
{
	std::cout << "Enter file name> ";
	char filename[128];
	std::cin >> filename;
	B3DFile res;
	if(!ReadB3DFile(filename, &res)){std::cin.get(); std::cin.get(); exit(0);}
	std::cout << "File: " << res.filename.c_str() << " Version: " << res.version << std::endl;
	std::cout << "B3DFile data:" << std::endl;
	for(int i = 0; i < res.textures.size(); i++)
	{
		std::cout << " Texture: " << res.textures[i].file << std::endl;
	}
	for(i = 0; i < res.materials.size(); i++)
	{
		std::cout << " Material: " << res.materials[i].name << std::endl;
	}
	for(i = 0; i < res.nodes.size(); i++)
	{
		std::cout << " Node: " << res.nodes[i].name << " Meshes - " << res.nodes[i].meshes.size() << " Bones - " << res.nodes[i].bones.size() << " Nodes - " << res.nodes[i].nodes.size() << std::endl;
		for(int j = 0; j < res.nodes[i].meshes.size(); j++)
		{
			std::cout << " Mesh vertexs: " << res.nodes[i].meshes[j].vertexs.size() << " Triangles: " << res.nodes[i].meshes[j].tris.size() << std::endl;
		}
		PrintSubnodes(&res.nodes[i], 2);
	}
	std::cin.get();
	std::cin.get();
	return 0;
}